/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.threadsample; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.widget.CursorAdapter; import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.GridView; import java.net.MalformedURLException; import java.net.URL; import java.util.concurrent.RejectedExecutionException; /** * PhotoThumbnailFragment displays a GridView of picture thumbnails downloaded from Picasa */ public class PhotoThumbnailFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemClickListener { private static final String STATE_IS_HIDDEN = "com.example.android.threadsample.STATE_IS_HIDDEN"; // The width of each column in the grid private int mColumnWidth; // A Drawable for a grid cell that's empty private Drawable mEmptyDrawable; // The GridView for displaying thumbnails private GridView mGridView; // Denotes if the GridView has been loaded private boolean mIsLoaded; // Intent for starting the IntentService that downloads the Picasa featured picture RSS feed private Intent mServiceIntent; // An adapter between a Cursor and the Fragment's GridView private GridViewAdapter mAdapter; // The URL of the Picasa featured picture RSS feed, in String format private static final String PICASA_RSS_URL = "http://picasaweb.google.com/data/feed/base/featured?" + "alt=rss&kind=photo&access=public&slabel=featured&hl=en_US&imgmax=1600"; private static final String[] PROJECTION = { DataProviderContract._ID, DataProviderContract.IMAGE_THUMBURL_COLUMN, DataProviderContract.IMAGE_URL_COLUMN }; // Constants that define the order of columns in the returned cursor private static final int IMAGE_THUMBURL_CURSOR_INDEX = 1; private static final int IMAGE_URL_CURSOR_INDEX = 2; // Identifies a particular Loader being used in this component private static final int URL_LOADER = 0; /* * This callback is invoked when the framework is starting or re-starting the Loader. It * returns a CursorLoader object containing the desired query */ @Override public Loader<Cursor> onCreateLoader(int loaderID, Bundle bundle) { /* * Takes action based on the ID of the Loader that's being created */ switch (loaderID) { case URL_LOADER: // Returns a new CursorLoader return new CursorLoader( getActivity(), // Context DataProviderContract.PICTUREURL_TABLE_CONTENTURI, // Table to query PROJECTION, // Projection to return null, // No selection clause null, // No selection arguments null // Default sort order ); default: // An invalid id was passed in return null; } } /* * This callback is invoked when the the Fragment's View is being loaded. It sets up the View. */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) { // Always call the super method first super.onCreateView(inflater, viewGroup, bundle); /* * Inflates the View from the gridlist layout file, using the layout parameters in * "viewGroup" */ View localView = inflater.inflate(R.layout.gridlist, viewGroup, false); // Sets the View's data adapter to be a new GridViewAdapter mAdapter = new GridViewAdapter(getActivity()); // Gets a handle to the GridView in the layout mGridView = ((GridView) localView.findViewById(android.R.id.list)); // Instantiates a DisplayMetrics object DisplayMetrics localDisplayMetrics = new DisplayMetrics(); // Gets the current display metrics from the current Window getActivity().getWindowManager().getDefaultDisplay().getMetrics(localDisplayMetrics); /* * Gets the dp value from the thumbSize resource as an integer in dps. The value can * be adjusted for specific display sizes, etc. in the dimens.xml file for a particular * values-<qualifier> directory */ int pixelSize = getResources().getDimensionPixelSize(R.dimen.thumbSize); /* * Calculates a width scale factor from the pixel width of the current display and the * desired pixel size */ int widthScale = localDisplayMetrics.widthPixels / pixelSize; // Calculates the grid column width mColumnWidth = (localDisplayMetrics.widthPixels / widthScale); // Sets the GridView's column width mGridView.setColumnWidth(mColumnWidth); // Starts by setting the GridView to have no columns mGridView.setNumColumns(-1); // Sets the GridView's data adapter mGridView.setAdapter(mAdapter); /* * Sets the GridView's click listener to be this class. As a result, when users click the * GridView, PhotoThumbnailFragment.onClick() is invoked. */ mGridView.setOnItemClickListener(this); /* * Sets the "empty" View for the layout. If there's nothing to show, a ProgressBar * is displayed. */ mGridView.setEmptyView(localView.findViewById(R.id.progressRoot)); // Sets a dark background to show when no image is queued to be downloaded mEmptyDrawable = getResources().getDrawable(R.drawable.imagenotqueued); // Initializes the CursorLoader getLoaderManager().initLoader(URL_LOADER, null, this); /* * Creates a new Intent to send to the download IntentService. The Intent contains the * URL of the Picasa feature picture RSS feed */ mServiceIntent = new Intent(getActivity(), RSSPullService.class) .setData(Uri.parse(PICASA_RSS_URL)); // If there's no pre-existing state for this Fragment if (bundle == null) { // If the data wasn't previously loaded if (!this.mIsLoaded) { // Starts the IntentService to download the RSS feed data getActivity().startService(mServiceIntent); } // If this Fragment existed previously, gets its state } else if (bundle.getBoolean(STATE_IS_HIDDEN, false)) { // Begins a transaction FragmentTransaction localFragmentTransaction = getFragmentManager().beginTransaction(); // Hides the Fragment localFragmentTransaction.hide(this); // Commits the transaction localFragmentTransaction.commit(); } // Returns the View inflated from the layout return localView; } /* * This callback is invoked when the Fragment is being destroyed. */ @Override public void onDestroyView() { // Sets variables to null, to avoid memory leaks mGridView = null; // If the EmptyDrawable contains something, sets those members to null if (mEmptyDrawable != null) { this.mEmptyDrawable.setCallback(null); this.mEmptyDrawable = null; } // Always call the super method last super.onDestroyView(); } /* * This callback is invoked after onDestroyView(). It clears out variables, shuts down the * CursorLoader, and so forth */ @Override public void onDetach() { // Destroys variables and references, and catches Exceptions try { getLoaderManager().destroyLoader(0); if (mAdapter != null) { mAdapter.changeCursor(null); mAdapter = null; } } catch (Throwable localThrowable) { } // Always call the super method last super.onDetach(); return; } /* * This is invoked whenever the visibility state of the Fragment changes */ @Override public void onHiddenChanged(boolean viewState) { super.onHiddenChanged(viewState); } /* * Implements OnItemClickListener.onItemClick() for this View's listener. * This implementation detects the View that was clicked and retrieves its picture URL. */ @Override public void onItemClick(AdapterView<?> adapterView, View view, int viewId, long rowId) { // Returns a one-row cursor for the data that backs the View that was clicked. Cursor cursor = (Cursor) mAdapter.getItem(viewId); // Retrieves the urlString from the cursor String urlString = cursor.getString(IMAGE_URL_CURSOR_INDEX); /* * Creates a new Intent to get the full picture for the thumbnail that the user clicked. * The full photo is loaded into a separate Fragment */ Intent localIntent = new Intent(Constants.ACTION_VIEW_IMAGE) .setData(Uri.parse(urlString)); // Broadcasts the Intent to receivers in this app. See DisplayActivity.FragmentDisplayer. LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(localIntent); } /* * Invoked when the CursorLoader finishes the query. A reference to the Loader and the * returned Cursor are passed in as arguments */ @Override public void onLoadFinished(Loader<Cursor> loader, Cursor returnCursor) { /* * Changes the adapter's Cursor to be the results of the load. This forces the View to * redraw. */ mAdapter.changeCursor(returnCursor); } /* * Invoked when the CursorLoader is being reset. For example, this is called if the * data in the provider changes and the Cursor becomes stale. */ @Override public void onLoaderReset(Loader<Cursor> loader) { // Sets the Adapter's backing data to null. This prevents memory leaks. mAdapter.changeCursor(null); } /* * This callback is invoked when the system has to destroy the Fragment for some reason. It * allows the Fragment to save its state, so the state can be restored later on. */ @Override public void onSaveInstanceState(Bundle bundle) { // Saves the show-hide status of the display bundle.putBoolean(STATE_IS_HIDDEN, isHidden()); // Always call the super method last super.onSaveInstanceState(bundle); } // Sets the state of the loaded flag public void setLoaded(boolean loadState) { mIsLoaded = loadState; } /** * Defines a custom View adapter that extends CursorAdapter. The main reason to do this is to * display images based on the backing Cursor, rather than just displaying the URLs that the * Cursor contains. */ private class GridViewAdapter extends CursorAdapter { /** * Simplified constructor that calls the super constructor with the input Context, * a null value for Cursor, and no flags * @param context A Context for this object */ public GridViewAdapter(Context context) { super(context, null, false); } /** * * Binds a View and a Cursor * * @param view An existing View object * @param context A Context for the View and Cursor * @param cursor The Cursor to bind to the View, representing one row of the returned query. */ @Override public void bindView(View view, Context context, Cursor cursor) { // Gets a handle to the View PhotoView localImageDownloaderView = (PhotoView) view.getTag(); // Converts the URL string to a URL and tries to retrieve the picture try { // Gets the URL URL localURL = new URL( cursor.getString(IMAGE_THUMBURL_CURSOR_INDEX) ) ; /* * Invokes setImageURL for the View. If the image isn't already available, this * will download and decode it. */ localImageDownloaderView.setImageURL( localURL, true, PhotoThumbnailFragment.this.mEmptyDrawable); // Catches an invalid URL } catch (MalformedURLException localMalformedURLException) { localMalformedURLException.printStackTrace(); // Catches errors trying to download and decode the picture in a ThreadPool } catch (RejectedExecutionException localRejectedExecutionException) { } } /** * Creates a new View that shows the contents of the Cursor * * * @param context A Context for the View and Cursor * @param cursor The Cursor to display. This is a single row of the returned query * @param viewGroup The viewGroup that's the parent of the new View * @return the newly-created View */ @Override public View newView(Context context, Cursor cursor, ViewGroup viewGroup) { // Gets a new layout inflater instance LayoutInflater inflater = LayoutInflater.from(context); /* * Creates a new View by inflating the specified layout file. The root ViewGroup is * the root of the layout file. This View is a FrameLayout */ View layoutView = inflater.inflate(R.layout.galleryitem, null); /* * Creates a second View to hold the thumbnail image. */ View thumbView = layoutView.findViewById(R.id.thumbImage); /* * Sets layout parameters for the layout based on the layout parameters of a virtual * list. In addition, this sets the layoutView's width to be MATCH_PARENT, and its * height to be the column width? */ layoutView.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, PhotoThumbnailFragment.this.mColumnWidth)); // Sets the layoutView's tag to be the same as the thumbnail image tag. layoutView.setTag(thumbView); return layoutView; } } }